/**
 * 
 */
package edu.umd.clip.lm.util;

import java.io.IOException;
import java.nio.*;
import java.nio.channels.WritableByteChannel;
import java.util.*;


/**
 * @author Denis Filimonov <den@cs.umd.edu>
 *
 */
public class GrowingOutputBuffer {
	private final int bufferSize;
	private List<ByteBuffer> buffers;
	private ByteBuffer currentBuffer;
	
	public GrowingOutputBuffer(int bufferSize, List<ByteBuffer> buffers) {
		this.bufferSize = bufferSize;
		this.buffers = buffers;
		if (!buffers.isEmpty()) {
			currentBuffer = buffers.get(buffers.size()-1);
		}
		
	}

	public GrowingOutputBuffer(int bufferSize) {
		this(bufferSize, new LinkedList<ByteBuffer>());
	}
	
	private void allocateNewBuffer(int size) {
		ByteBuffer buffer = ByteBuffer.allocate(size);
		buffers.add(buffer);
		currentBuffer = buffer;
	}
	
	private void ensureSize(int size) {
		int theSize = size > bufferSize ? size : bufferSize;
		if (currentBuffer == null || currentBuffer.remaining() < size) {
			allocateNewBuffer(theSize);
		}
	}

	public void put(byte b) {
		ensureSize(1);
		currentBuffer.put(b);
	}

	public void put(byte[] src, int offset, int length) {
		ensureSize(length);
		currentBuffer.put(src, offset, length);
	}

	public final void put(byte[] src) {
		put(src, 0, src.length);
	}

	public void put(ByteBuffer src) {
		if (currentBuffer == null || currentBuffer.remaining() < src.remaining()) {
			ByteBuffer buffer = ByteBuffer.allocate(Math.max(bufferSize, src.remaining()));
			buffers.add(buffer);
			currentBuffer = buffer;			
		}
		currentBuffer.put(src);
	}

	public void put(GrowingOutputBuffer buffer) {
		buffers.addAll(buffer.buffers);
		currentBuffer = buffers.get(buffers.size()-1);
	}
	
	public void putChar(char value) {
		ensureSize(2);
		currentBuffer.putChar(value);
	}

	public void putDouble(double value) {
		ensureSize(8);
		currentBuffer.putDouble(value);
	}

	public void putFloat(float value) {
		ensureSize(4);
		currentBuffer.putFloat(value);
	}

	public void putInt(int value) {
		ensureSize(4);
		currentBuffer.putInt(value);
	}

	public void putLong(long value) {
		ensureSize(8);
		currentBuffer.putLong(value);
	}

	public void putShort(short value) {
		ensureSize(2);
		currentBuffer.putShort(value);
	}

	public void flip() {
		for(ByteBuffer buffer : buffers) {
			buffer.flip();
		}
	}

	public int getBufferSize() {
		return bufferSize;
	}

	public List<ByteBuffer> getBuffers() {
		return buffers;
	}
	
	public int size() {
		int size = 0;
		for(ByteBuffer buffer : buffers) {
			size += buffer.position();
		}
		return size;
	}
	
	public long drainTo(WritableByteChannel output) throws IOException {
		long totalBytesWritten = 0;
		
		for(ByteBuffer b : buffers) {
			while(b.hasRemaining()) {
				long bytesWritten = output.write(b);
				totalBytesWritten += bytesWritten;
				//System.err.printf("%d bytes written\n", bytesWritten);
			}
		}
		
		buffers.clear();
		currentBuffer = null;

		return totalBytesWritten;
	}
}
